package com.mezz.bones.framework.web.service;

import com.mezz.bones.framework.cache.redis.service.RedisService;
import com.mezz.bones.framework.base.util.ExecutionTimeLoggerUtil;
import com.mezz.bones.framework.web.domain.RequestLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ApiStatisticsService {

    private static final long EXPIRE_DAY = 7; // 7 天过期时间
    private static final int BATCH_SIZE = 500; // 批量处理的大小

    @Resource
    private RedisService redisService;

    private List<RequestLog> requestLogList = new CopyOnWriteArrayList<>();

    /**
     * 统计接口数据
     */
    public void statistics(RequestLog requestLog) {
        requestLogList.add(requestLog);

        // 如果列表大小达到阈值，批量保存到 Redis
        if (requestLogList.size() >= BATCH_SIZE) {
            saveStatistics(requestLogList);
        }
    }

    /**
     * 批量保存统计数据到 Redis
     */
    private synchronized void saveStatistics(List<RequestLog> logs) {

        ExecutionTimeLoggerUtil.logExecutionTime("保存API统计数据", () -> {

            if(logs.isEmpty()){
                return;
            }
            // 时间维度
            String hour = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
            String day = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));

            // 请求数量统计
            long requestCount = logs.size();
            incrementRequestCount("hour:" + hour, requestCount);
            incrementRequestCount("day:" + day, requestCount);

            // 更新 Top 10 最长响应时间接口
            for (RequestLog log : logs) {
                updateLongestExecution("hour:" + hour, log.getReqUrl(), log.getExecTime());
                updateLongestExecution("day:" + day, log.getReqUrl(), log.getExecTime());
            }
            requestLogList.clear(); // 清空缓存
        });

    }

    /**
     * 增加请求总量
     */
    private void incrementRequestCount(String dimension, long count) {
        String key = String.format("api:count:%s", dimension);

        redisService.opsForValue().increment(key, count);
        redisService.expire(key, EXPIRE_DAY, TimeUnit.DAYS);
    }

    /**
     * 更新最长执行时间接口
     */
    private void updateLongestExecution(String dimension, String apiPath, long executionTime) {
        String key = String.format("api:exec:time:top:%s", dimension);

        // 反转执行时间，将执行时间较长的放到后面
        redisService.opsForZSet().add(key, apiPath, -executionTime);  // 这里加了负号

        // 设置 ZSet 的过期时间
        redisService.expire(key, EXPIRE_DAY, TimeUnit.DAYS);

        // 保持 Top 10 记录，删除排名第 11 到最后的元素
        long maxSize = 10;
        redisService.opsForZSet().removeRange(key, maxSize, -1);
    }

    /**
     * 定时任务：每小时保存统计数据
     */
    @Scheduled(cron = "0 59 * * * ?")  // 每小时的第59分钟执行一次
//    @Scheduled(cron = "*/5 * * * * ?")  // 每5秒执行一次
    public void saveHourlyStatistics() {
        if (!requestLogList.isEmpty()) {
            saveStatistics(requestLogList);
        }
    }

    /**
     * 每小时的 5 分钟打印数据
     */
    @Scheduled(cron = "0 5 * * * ?")  // 每小时的第5分钟执行一次
//    @Scheduled(cron = "*/5 * * * * ?")  // 每5秒执行一次
    public void printHourlyData() {
        String hour = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));

        // 请求数量
        Integer requestCount = getRequestCount("hour:" + hour);

        // 获取 Top 10 最长响应时间接口，并包含执行时间
        Set<ZSetOperations.TypedTuple<String>> topApisWithExecutionTime = getTopApisWithExecutionTime("hour:" + hour);

        // 格式化输出
        StringBuilder topApisLog = new StringBuilder();
        for (ZSetOperations.TypedTuple tuple : topApisWithExecutionTime) {
            Object apiPath = tuple.getValue();  // 获取接口路径
            Double executionTime = -tuple.getScore();  // 获取接口执行时间
            topApisLog.append(String.format("API: %s, Execution Time: %.2f milliseconds\n", apiPath, executionTime));
        }

        // 打印日志
        log.info(String.format("Hourly Data - Hour: %s, Request Count: %d\nTop 10 APIs with Execution Time:\n%s",
                hour, requestCount, topApisLog.toString()));
    }

    /**
     * 获取请求数量（示例方法，需要根据实际实现获取 Redis 中的数据）
     */
    private Integer getRequestCount(String key) {
        String redisKey = "api:count:" + key;
        Object o = redisService.opsForValue().get(redisKey);
        return (Integer) o;
    }

    /**
     * 获取 Top 10 响应时间最长的接口（示例方法，需要根据实际实现获取 Redis 中的数据）
     */
    private Set<ZSetOperations.TypedTuple<String>> getTopApisWithExecutionTime(String key) {
        String redisKey = "api:exec:time:top:" + key;
        return redisService.opsForZSet().rangeWithScores(redisKey, 0, 9);  // 获取 Top 10 最慢的接口
    }

    /**
     * 每天的 11:30 和 23:30 打印数据
     */
    @Scheduled(cron = "30 11,23 * * * ?")  // 每天的 11:30 和 23:30 执行一次
//    @Scheduled(cron = "*/5 * * * * ?")  // 每5秒执行一次
    public void printDailyData() {
        String day = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));

        // 请求数量
        Integer requestCount = getRequestCount("day:" + day);

        // 获取 Top 10 最长响应时间接口，并包含执行时间
        Set<ZSetOperations.TypedTuple<String>> topApisWithExecutionTime = getTopApisWithExecutionTime("day:" + day);

        // 格式化输出
        StringBuilder topApisLog = new StringBuilder();
        for (ZSetOperations.TypedTuple<String> tuple : topApisWithExecutionTime) {
            String apiPath = tuple.getValue();  // 获取接口路径
            Double executionTime = -tuple.getScore();  // 获取接口执行时间
            topApisLog.append(String.format("API: %s, Execution Time: %.2f milliseconds\n", apiPath, executionTime));
        }

        // 打印日志
        log.info(String.format("Daily Data - Day: %s, Request Count: %d\nTop 10 APIs with Execution Time:\n%s",
                day, requestCount, topApisLog.toString()));
    }

}
